/*
* Copyright (c) 2012 Kyle Hayward
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "Precompiled.h"

#include "Traversal.h"
#include "Context.h"

using namespace System;
using namespace OptixDotNet::Math;

namespace OptixDotNet {
namespace Traversal {

	/// <summary>
	/// Provides types and methods for creating Optix Traversal batched ray-tracing programs
	/// </summary>
	private ref class NamespaceDoc
    {
    };

	Traversal::Traversal( Context^ context, QueryType query, RayFormat rayFormat, TriFormat triFormat, TraversalOutput outputs, InitOptions options )
	{
		mRayFormat = rayFormat;
		mRaySize = mRayFormat == RayFormat::OriginDirectionInterleaved ? sizeof( Ray ) : sizeof( RayMinMax );

		pin_ptr<RTUtraversal> ptr = &mTraversal;
		rtuTraversalCreate( ptr, static_cast<RTUquerytype>(query), static_cast<RTUrayformat>(rayFormat), static_cast<RTUtriformat>(triFormat), 
			static_cast<unsigned int>( outputs ), static_cast<unsigned int>( options ), context->InternalPtr() );
	}

	Traversal::~Traversal()
	{

	}

	Traversal::!Traversal()
	{
		Destroy();
	}

	generic<typename T> where T : value class
	void Traversal::SetMesh( unsigned int numVerts, array<T>^ verts, unsigned int numTris, array<unsigned int>^ indices )
	{
		pin_ptr<T> vPtr = &verts[0];
		pin_ptr<unsigned int> iPtr = &indices[0];

		if( ( numVerts * sizeof( float ) * 3 ) != ( sizeof( T ) * verts->Length ) )
			throw gcnew ArgumentOutOfRangeException( "numVerts", "numVerts does not match the number of verts provided" );

		if( numTris != ( indices->Length / 3 ) )
			throw gcnew ArgumentOutOfRangeException( "numTris", "numTris does not match the number of tris provided" );

		CheckError( rtuTraversalSetMesh( mTraversal, numVerts, (float*)vPtr, numTris, iPtr ) );
	}

	generic<typename T, typename U> where T : value class
	void Traversal::SetMesh( unsigned int numVerts, array<T>^ verts, unsigned int numTris, array<U>^ indices )
	{
		pin_ptr<T> vPtr = &verts[0];
		pin_ptr<U> iPtr = &indices[0];

		if( ( numVerts * sizeof( float ) * 3 ) != ( sizeof( T ) * verts->Length ) )
			throw gcnew ArgumentOutOfRangeException( "numVerts", "numVerts does not match the number of verts provided" );

		if( ( numTris * sizeof( unsigned int ) * 3 ) != ( sizeof( U ) * indices->Length ) )
			throw gcnew ArgumentOutOfRangeException( "numTris", "numTris does not match the number of tris provided" );


		CheckError( rtuTraversalSetMesh( mTraversal, verts->Length, (float*)vPtr, numTris, (unsigned int*)iPtr ) );
	}

	generic<typename T> where T : value class
	void Traversal::SetTriangles( array<T>^ tris )
	{
		pin_ptr<T> tPtr = &tris[0];

		CheckError( rtuTraversalSetTriangles( mTraversal, tris->Length, (float*)tPtr ) );
	}

	generic< typename T > where T : value class
	TraversalStream<T> Traversal::MapRays( unsigned int numRays )
	{
		mCurrentNumRays = numRays;

		float* rays;
		CheckError( rtuTraversalMapRays( mTraversal, numRays, &rays ) );

		BufferStream^ stream = gcnew BufferStream( rays, mRaySize * numRays, true, true, false );
		return TraversalStream<T>( stream );
	}

	void Traversal::UnmapRays()
	{
		CheckError( rtuTraversalUnmapRays( mTraversal ) );
	}

	void Traversal::SetRayData( array<Ray>^ rays )
	{
		SetRayData<Ray>( rays );
	}

	void Traversal::SetRayData( array<RayMinMax>^ rays )
	{
		SetRayData<RayMinMax>( rays );
	}

	generic< typename T > where T : value class
	void Traversal::SetRayData( array<T>^ rays )
	{
		if( rays == nullptr || rays->Length == 0)
			return;		

		float* rayData = 0;
		pin_ptr<T> rayPtr = &rays[0];

		CheckError( rtuTraversalMapRays( mTraversal, rays->Length, &rayData ) );

		if( rayData == 0 )
			throw gcnew Exception( "Traversal Error: Ray buffer cannot be mapped" );

		memcpy( rayData, rayPtr, rays->Length * sizeof( T ) );

		CheckError( rtuTraversalUnmapRays( mTraversal ) );

		mCurrentNumRays = rays->Length;
	}

	TraversalStream<TraversalResult> Traversal::MapResults()
	{
		RTUtraversalresult* results;

		CheckError( rtuTraversalMapResults( mTraversal, &results ) );

		if( results == 0 )
			throw gcnew Exception( "Traversal Error: Results buffer cannot be mapped" );

		BufferStream^ stream = gcnew BufferStream( results, sizeof( RTUtraversalresult ) * mCurrentNumRays, true, false, false );
		return TraversalStream<TraversalResult>( stream );
	}

	void Traversal::UnmapResults()
	{
		CheckError( rtuTraversalUnmapResults( mTraversal ) );
	}

	void Traversal::GetResults( array<TraversalResult>^ results )
	{
		if( results == nullptr || results->Length == 0)
			return;		

		RTUtraversalresult* resultData;
		pin_ptr<TraversalResult> resultsPtr = &results[0];

		CheckError( rtuTraversalMapResults( mTraversal, &resultData ) );

		if( resultData == 0 )
			throw gcnew Exception( "Traversal Error: Results buffer cannot be mapped" );

		memcpy( resultsPtr, resultData, mCurrentNumRays * sizeof( RTUtraversalresult ) );

		CheckError( rtuTraversalUnmapResults( mTraversal ) );
	}

	generic< typename T > where T : value class
	TraversalStream<T> Traversal::MapBaryCentricOutput()
	{
		void* output;
		CheckError( rtuTraversalMapOutput( mTraversal, RTU_OUTPUT_BARYCENTRIC, &output ) );

		if( output == 0 )
			throw gcnew Exception( "Traversal Error: BaryCentricOutput buffer cannot be mapped" );

		BufferStream^ stream = gcnew BufferStream( output, sizeof( T ) * mCurrentNumRays, true, false, false );
		return TraversalStream<T>( stream );
	}

	void Traversal::UnmapBaryCentricOutput()
	{
		CheckError( rtuTraversalUnmapOutput( mTraversal, RTU_OUTPUT_BARYCENTRIC ) );
	}

	void Traversal::GetBaryCentricOutput( array<Vector2>^ output )
	{
		if( output->Length != mCurrentNumRays )
			throw gcnew ArgumentOutOfRangeException( "output", "Output array must equal number of rays set on the Traversal object" );

		pin_ptr<Vector2> outputPtr = &output[0];
		void* outputData;
		CheckError( rtuTraversalMapOutput( mTraversal, RTU_OUTPUT_BARYCENTRIC, &outputData ) );

		if( outputData == 0 )
			throw gcnew Exception( "Traversal Error: BaryCentricOutput buffer cannot be mapped" );

		memcpy( outputPtr, outputData, mCurrentNumRays * sizeof( Vector2 ) );

		CheckError( rtuTraversalUnmapOutput( mTraversal, RTU_OUTPUT_BARYCENTRIC ) );
	}

	generic< typename T > where T : value class
	TraversalStream<T> Traversal::MapNormalOutput()
	{
		void* output;
		CheckError( rtuTraversalMapOutput( mTraversal, RTU_OUTPUT_NORMAL, &output ) );

		if( output == 0 )
			throw gcnew Exception( "Traversal Error: NormalOutput buffer cannot be mapped" );

		BufferStream^ stream = gcnew BufferStream( output, sizeof( T ) * mCurrentNumRays, true, false, false );
		return TraversalStream<T>( stream );
	}

	void Traversal::UnmapNormalOutput()
	{
		CheckError( rtuTraversalUnmapOutput( mTraversal, RTU_OUTPUT_NORMAL ) );
	}

	void Traversal::GetNormalOutput( array<Vector3>^ output )
	{
		if( output->Length != mCurrentNumRays )
			throw gcnew ArgumentOutOfRangeException( "output", "Output array must equal number of rays set on the Traversal object" );

		pin_ptr<Vector3> outputPtr = &output[0];
		void* outputData;
		CheckError( rtuTraversalMapOutput( mTraversal, RTU_OUTPUT_NORMAL, &outputData ) );

		if( outputData == 0 )
			throw gcnew Exception( "Traversal Error: NormalOutput buffer cannot be mapped" );

		memcpy( outputPtr, outputData, mCurrentNumRays * sizeof( Vector3 ) );

		CheckError( rtuTraversalUnmapOutput( mTraversal, RTU_OUTPUT_NORMAL ) );
	}

	TraversalStream<char> Traversal::MapBackFacingOutput()
	{
		void* output;
		CheckError( rtuTraversalMapOutput( mTraversal, RTU_OUTPUT_BACKFACING, &output ) );

		if( output == 0 )
			throw gcnew Exception( "Traversal Error: BackFacingOutput buffer cannot be mapped" );

		BufferStream^ stream = gcnew BufferStream( output, sizeof( char ) * mCurrentNumRays, true, false, false );
		return TraversalStream<char>( stream );
	}

	void Traversal::UnmapBackFacingOutput()
	{
		CheckError( rtuTraversalUnmapOutput( mTraversal, RTU_OUTPUT_BACKFACING ) );
	}

	void Traversal::GetBackFacingOutput( array<char>^ output )
	{
		if( output->Length != mCurrentNumRays )
			throw gcnew ArgumentOutOfRangeException( "output", "Output array must equal number of rays set on the Traversal object" );

		pin_ptr<char> outputPtr = &output[0];
		void* outputData;
		CheckError( rtuTraversalMapOutput( mTraversal, RTU_OUTPUT_BACKFACING, &outputData ) );

		if( outputData == 0 )
			throw gcnew Exception( "Traversal Error: BackFacingOutput buffer cannot be mapped" );

		memcpy( outputPtr, outputData, mCurrentNumRays * sizeof( char ) );

		CheckError( rtuTraversalUnmapOutput( mTraversal, RTU_OUTPUT_BACKFACING ) );
	}

	void Traversal::Preprocess()
	{
		CheckError( rtuTraversalPreprocess( mTraversal ) );
	}

	void Traversal::Traverse()
	{
		CheckError( rtuTraversalTraverse( mTraversal ) );
	}

	BufferStream^ Traversal::AccelData::get()
	{
		Size size = AccelDataSize;

		char* data = new char[ size ];
		CheckError( rtuTraversalGetAccelData( mTraversal, data ) );

		return gcnew BufferStream( data, size, true, false, true );
	}

	void Traversal::AccelData::set( BufferStream^ stream )
	{
		CheckError( rtuTraversalSetAccelData( mTraversal, (void*)stream->DataPointer.ToPointer(), (RTsize)stream->Length ) );
	}
}
}